package com.astersoft.lib.util;

/*
 * Copyright 2012 AsterSoft personal developer - Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import static java.util.Collections.emptyList;
import static java.util.regex.Pattern.quote;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.astersoft.lib.ASMiscellaneaUtil;

/**
 * 
 * @author dario.durzo
 * @version 0.1
 * @created.date 2009.08.26
 */
public class ASCollectionUtil {
	static final String DEFAULT_DELIMETER = ", ";

	public static String toString(Collection<?> collection) {
		return ASCollectionUtil.toString(collection, true);
	}

	public static String toString(Collection<?> collection, boolean frame) {
		return ASCollectionUtil.toString(collection, frame, ASCollectionUtil.DEFAULT_DELIMETER);
	}

	public static String toString(Collection<?> collection, boolean frame, String delimeter) {
		return ASCollectionUtil.toString(collection.toArray(new Object[collection.size()]), frame, delimeter);
	}

	public static String toString(double[] array) {
		StringBuilder b = new StringBuilder();
		b.append('[');
		for (int i = 0; i < array.length; i++) {
			b.append(array[i]);
			if (i < array.length - 1) {
				b.append(", ");
			}
		}
		b.append(']');
		return b.toString();
	}

	public static String toString(int[] array) {
		StringBuilder b = new StringBuilder();
		b.append('[');
		for (int i = 0; i < array.length; i++) {
			b.append(array[i]);
			if (i < array.length - 1) {
				b.append(", ");
			}
		}
		b.append(']');
		return b.toString();
	}

	public static StringBuilder toString(Map<?, ?> map) {
		return toString(map, "");
	}

	public static StringBuilder toString(Map<?, ?> map, String linePrefix) {
		StringBuilder sb = new StringBuilder();
		if (map == null) {
			return sb.append("null");
		}
		for (Entry<?, ?> entry : map.entrySet()) {
			sb.append(linePrefix).append(entry.getKey()).append(" => ").append(entry.getValue()).append(System.getProperty("line.separator"));
		}
		return sb;
	}

	/**
	 * Converts an array to a String represenation, using the toString() method of each array element.
	 */
	public static String toString(Object[] array) {
		return toString(array, true);
	}

	public static String toString(Object[] array, boolean frame) {
		return toString(array, frame, ASCollectionUtil.DEFAULT_DELIMETER);
	}

	public static String toString(Object[] array, boolean frame, String delimeter) {
		StringBuilder b = new StringBuilder();
		if (frame) {
			b.append('[');
		}
		for (int i = 0; i < array.length; i++) {
			b.append(array[i]);
			if (i < array.length - 1) {
				b.append(delimeter);
			}
		}
		if (frame) {
			b.append(']');
		}
		return b.toString();
	}

	public static String toString(Object[] array, String delimeter) {
		return toString(array, true, delimeter);
	}

	public static <E> ArrayList<E> toList(E... elements) {
		return addAll(new ArrayList<E>(elements.length + 10), elements);
	}

	public static <E> ArrayList<E> addAll(ArrayList<E> list, E... elements) {
		for (E e : elements) {
			list.add(e);
		}
		return list;
	}

	@SuppressWarnings("rawtypes")
	public static class MapFieldComparator implements Comparator<Map<String, Comparable>> {
		private String field;

		public MapFieldComparator(String field) {
			this.field = field;
		}

		@SuppressWarnings("unchecked")
		public int compare(Map<String, Comparable> o1, Map<String, Comparable> o2) {
			return o1.get(field).compareTo(o2.get(field));
		}
	}


	/**
	 * Sort a map by it's keys in ascending order. 
	 *  
	 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
	 * @author Maxim Veksler
	 */
	public static <K, V> Map<K, V> sortMapByKey(final Map<K, V> map) {
		return sortMapByKey(map, SortingOrder.ASCENDING);
	}

	/**
	 * Sort a map by it's values in ascending order.
	 *  
	 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
	 * @author Maxim Veksler
	 */
	public static <K, V> Map<K, V> sortMapByValue(final Map<K, V> map) {
		return sortMapByValue(map, SortingOrder.ASCENDING);
	}

	/**
	 * Sort a map by it's keys.
	 *  
	 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
	 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
	 * @author Maxim Veksler
	 */
	public static <K, V> Map<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) {
		Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
			public int compare(Entry<K, V> o1, Entry<K, V> o2) {
				return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder);
			}
		};

		return sortMap(map, comparator);
	}

	/**
	 * Sort a map by it's values.
	 *  
	 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
	 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
	 * @author Maxim Veksler
	 */
	public static <K, V> Map<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) {
		Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
			public int compare(Entry<K, V> o1, Entry<K, V> o2) {
				return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder);
			}
		};

		return sortMap(map, comparator);
	}

	@SuppressWarnings("unchecked")
	private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) {
		int compare = ((Comparable<T>)o1).compareTo(o2);

		switch (sortingOrder) {
		case ASCENDING:
			return compare;
		case DESCENDING:
			return (-1) * compare;
		}

		return 0;
	}

	/**
	 * Sort a map by supplied comparator logic.
	 *  
	 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
	 * @author Maxim Veksler
	 */
	public static <K, V> Map<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) {
		// Convert the map into a list of key,value pairs.
		List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet());

		// Sort the converted list according to supplied comparator.
		Collections.sort(mapEntries, comparator);

		// Build a new ordered map, containing the same entries as the old map.  
		LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20));
		for(Map.Entry<K, V> entry : mapEntries) {
			// We iterate on the mapEntries list which is sorted by the comparator putting new entries into 
			// the targeted result which is a sorted map. 
			result.put(entry.getKey(), entry.getValue());
		}

		return result;
	}
	
	/**
	 * Creates an unmodifiable set based on the given values. If one of the values is an instance of an array or a
	 * collection, then each of its values will also be merged into the set. Nested arrays or collections will result
	 * in a {@link ClassCastException}.
	 * @param values The values to create an unmodifiable set for.
	 * @return An unmodifiable set based on the given values.
	 * @throws ClassCastException When one of the values or one of the arrays or collections is of wrong type.
	 * @since 1.1
	 */
	@SuppressWarnings("unchecked")
	public static <T> Set<T> unmodifiableSet(Object... values) {
		Set<T> set = new HashSet<T>();

		for (Object value : values) {
			if (value instanceof Object[]) {
				for (Object item : (Object[]) value) {
					set.add((T) item);
				}
			}
			else if (value instanceof Collection<?>) {
				for (Object item : (Collection<?>) value) {
					set.add((T) item);
				}
			}
			else {
				set.add((T) value);
			}
		}

		return Collections.unmodifiableSet(set);
	}

	/**
	 * Converts comma separated values in a string into a list with those values.
	 * <p>
	 * E.g. a string with "foo, bar, kaz" will be converted into a <code>List</code>
	 * with values:
	 * <ul>
	 * <li>"foo"</li>
	 * <li>"bar"</li>
	 * <li>"kaz"</li>
	 * </ul>
	 *
	 * Note that whitespace will be stripped. Empty entries are not supported. This method defaults to
	 * using a comma (<code>","</code>) as delimiter. See {@link Utils#csvToList(String, String)} for when
	 * a different delimiter is needed.
	 *
	 * @param values string with comma separated values
	 * @return a list with all values encountered in the <code>values</argument>, can be the empty list.
	 * @since 1.4
	 */
	public static List<String> csvToList(String values) {
		return csvToList(values, ",");
	}

	/**
	 * Converts comma separated values in a string into a list with those values.
	 * <p>
	 * E.g. a string with "foo, bar, kaz" will be converted into a <code>List</code>
	 * with values:
	 * <ul>
	 * <li>"foo"</li>
	 * <li>"bar"</li>
	 * <li>"kaz"</li>
	 * </ul>
	 *
	 * Note that whitespace will be stripped. Empty entries are not supported.
	 *
	 * @param values string with comma separated values
	 * @param delimiter the delimiter used to separate the actual values in the <code>values</code> parameter.
	 * @return a list with all values encountered in the <code>values</argument>, can be the empty list.
	 * @since 1.4
	 */
	public static List<String> csvToList(String values, String delimiter) {

		if (ASMiscellaneaUtil.isEmpty(values)) {
			return emptyList();
		}

		List<String> list = new ArrayList<String>();

		for (String value : values.split(quote(delimiter))) {
			String trimmedValue = value.trim();
			if (!ASMiscellaneaUtil.isEmpty(trimmedValue)) {
				list.add(trimmedValue);
			}
		}

		return list;
	}

	/**
	 * Sorting order enum, specifying request result sort behavior.
	 * @author Maxim Veksler
	 *
	 */
	public static enum SortingOrder {
		/**
		 * Resulting sort will be from smaller to biggest.
		 */
		ASCENDING,
		/**
		 * Resulting sort will be from biggest to smallest.
		 */
		DESCENDING
	}
	
	public static void main(String[] args) {
		Map<String,Integer> mesi = new HashMap<String, Integer>();  
		mesi.clear();
    	mesi.put("Tutti i mesi", 0);
    	int i = 1;
    	for(String month: ASMiscellaneaUtil.nomiMese){
    		mesi.put(month, i++);
    	}
    	for(String k: mesi.keySet()) 
    		System.out.println(mesi.get(k));
    	
    	System.out.println("******************************");
    	Map<String, Integer> map = ASCollectionUtil.sortMapByValue(mesi, SortingOrder.ASCENDING);
    	
    	for(String k: map.keySet()) 
    		System.out.println(map.get(k));
    	
	}

}
